﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;

namespace Neovolve.Extensibility.ReSharper
{
    /// <summary>
    /// The <see cref="CrefAttributeDescriptor" />
    /// class is used to describe the parts of the value of a cref attribute.
    /// </summary>
    internal class CrefAttributeDescriptor
    {
        /// <summary>
        /// Defines the encoded close bracket value.
        /// </summary>
        private const String EncodedCloseBracket = "&gt;";

        /// <summary>
        /// Defines the encoded open bracket value.
        /// </summary>
        private const String EncodedOpenBracket = "&lt;";

        /// <summary>
        /// Defines the delimiters used to parse the contents of the cref attribute to identify types.
        /// </summary>
        private static readonly List<String> Delimiters = new List<String>
                                                              {
                                                                  "{",
                                                                  "}",
                                                                  ",",
                                                                  " ",
                                                                  ".",
                                                                  EncodedOpenBracket,
                                                                  EncodedCloseBracket
                                                              };

        /// <summary>
        /// Stores the parts of the cref attribute value.
        /// </summary>
        private readonly List<CrefAttributePart> _parts;

        /// <summary>
        /// Initializes a new instance of the <see cref="CrefAttributeDescriptor" /> class.
        /// </summary>
        /// <param name="value">The value.</param>
        public CrefAttributeDescriptor(String value)
        {
            _parts = ParseValue(value);
        }

        /// <summary>
        /// Returns a <see cref="T:System.String" /> that represents the current <see cref="T:System.Object" />.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String" /> that represents the current <see cref="T:System.Object" />.
        /// </returns>
        public override String ToString()
        {
            StringBuilder builder = new StringBuilder();

            // Loop through each part found
            for (Int32 index = 0; index < _parts.Count; index++)
            {
                CrefAttributePart part = _parts[index];

                builder.Append(part.Part);
            }

            return builder.ToString();
        }

        /// <summary>
        /// Determines the types and members.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="parsedValues">The parsed values.</param>
        private static void DetermineTypesAndMembers(String value, List<CrefAttributePart> parsedValues)
        {
            // Loop through each value found to detect Types
            for (Int32 partIndex = 0; partIndex < parsedValues.Count; partIndex++)
            {
                CrefAttributePart part = parsedValues[partIndex];

                if (part.PartType
                    != CrefPartTypes.Namespace)
                {
                    continue;
                }

                // If this is the last entry, or the next value is not a . delimiter when the previous one was, then this is a type
                if (partIndex == parsedValues.Count - 1)
                {
                    // This part will be a member if the original value started with M:, otherwise it will be a type
                    if (value.StartsWith("M:", StringComparison.OrdinalIgnoreCase))
                    {
                        part.PartType = CrefPartTypes.Member;
                    }
                    else
                    {
                        part.PartType = CrefPartTypes.Type;
                    }

                    // Store the updated part back in the list
                    parsedValues[partIndex] = part;
                }
                else if (parsedValues[partIndex + 1].PartType == CrefPartTypes.Delimiter
                         && parsedValues[partIndex + 1].Part != ".")
                {
                    if (partIndex > 0 && parsedValues[partIndex - 1].PartType == CrefPartTypes.Delimiter
                        && parsedValues[partIndex - 1].Part == ".")
                    {
                        part.PartType = CrefPartTypes.Type;

                        // Store the updated part back in the list
                        parsedValues[partIndex] = part;
                    }
                }
            }
        }

        /// <summary>
        /// Parses the value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns>
        /// A <see cref="List{CrefAttributePart}" /> instance.
        /// </returns>
        private static List<CrefAttributePart> ParseValue(String value)
        {
            List<CrefAttributePart> parsedValues = new List<CrefAttributePart>();
            String currentPart = String.Empty;

            for (Int32 index = 0; index < value.Length; index++)
            {
                String remainingValue = value.Substring(index);
                Boolean delimiterFound = false;

                // Loop through each delimiter
                for (Int32 delimiterIndex = 0; delimiterIndex < Delimiters.Count; delimiterIndex++)
                {
                    String delimiter = Delimiters[delimiterIndex];
                    Int32 delimiterLength = delimiter.Length;

                    // Check if there is enough lenth in the remaining string to check this delimiter
                    if (delimiterLength > remainingValue.Length)
                    {
                        // This delimiter can't be in the remaining value, skip to the next delimiter
                        continue;
                    }

                    // Check if the value at the current index starts with delimiter
                    if (remainingValue.StartsWith(delimiter, StringComparison.Ordinal))
                    {
                        // We have found a delimiter at the start of this index
                        if (String.IsNullOrEmpty(currentPart) == false)
                        {
                            // Store the part that has been calculated
                            CrefAttributePart newPart = new CrefAttributePart();

                            newPart.Part = currentPart;

                            // Default the part type to a namespace
                            newPart.PartType = CrefPartTypes.Namespace;

                            parsedValues.Add(newPart);
                            currentPart = String.Empty;
                        }

                        CrefAttributePart delimiterPart = new CrefAttributePart();

                        delimiterPart.Part = delimiter;
                        delimiterPart.PartType = CrefPartTypes.Delimiter;

                        parsedValues.Add(delimiterPart);

                        if (delimiterLength > 1)
                        {
                            // Kick out the index
                            index += delimiterLength - 1;
                        }

                        delimiterFound = true;

                        break;
                    }
                }

                if (delimiterFound == false)
                {
                    String nextCharacter = value.Substring(index, 1);

                    currentPart += nextCharacter;
                }
            }

            // Check if there is a remaining part
            if (String.IsNullOrEmpty(currentPart) == false)
            {
                // Store the part that has been calculated
                CrefAttributePart newPart = new CrefAttributePart();

                newPart.Part = currentPart;

                // Default the part type to a namespace
                newPart.PartType = CrefPartTypes.Namespace;

                parsedValues.Add(newPart);
            }

            DetermineTypesAndMembers(value, parsedValues);

            return parsedValues;
        }

        /// <summary>
        /// Gets or sets the <see cref="System.String" /> at the specified index.
        /// </summary>
        /// <param name="index">The index of the array.</param>
        /// <value>
        /// The value of the attribute definition at the specified index.
        /// </value>
        public CrefAttributePart this[Int32 index]
        {
            get
            {
                return _parts[index];
            }

            set
            {
                _parts[index] = value;
            }
        }

        /// <summary>
        /// Gets the length.
        /// </summary>
        /// <value>
        /// The length.
        /// </value>
        public Int32 Length
        {
            get
            {
                return _parts.Count;
            }
        }

        /// <summary>
        /// Gets the type references.
        /// </summary>
        /// <value>
        /// The type references.
        /// </value>
        public ReadOnlyCollection<String> TypeReferences
        {
            get
            {
                List<String> references = new List<String>();

                for (Int32 index = 0; index < Length; index++)
                {
                    if (this[index].PartType
                        == CrefPartTypes.Type)
                    {
                        references.Add(this[index].Part);
                    }
                }

                return references.AsReadOnly();
            }
        }
    }
}